{"cells": [{"cell_type": "markdown", "metadata": {}, "source": ["
\n", "
\n", "
\n", "
Computational Seismology
\n", "
Finite Differences Method with Optimal Operator - Acoustic 1D Case
\n", "
\n", "
\n", "
"]}, {"cell_type": "markdown", "metadata": {}, "source": ["\n", "\n", "

\n", "\n", "\n", "\n", "

\n", "\n", "\n", "---\n", "\n", "This notebook is part of the supplementary material \n", "to [Computational Seismology: A Practical Introduction](https://global.oup.com/academic/product/computational-seismology-9780198717416?cc=de&lang=en&#), \n", "Oxford University Press, 2016.\n", "\n", "##### Authors:\n", "* Heiner Igel ([@heinerigel](https://github.com/heinerigel))\n", "* Lion Krischer ([@krischer](https://github.com/krischer))\n", "* Taufiqurrahman ([@git-taufiqurrahman](https://github.com/git-taufiqurrahman))\n", "\n", "---"]}, {"cell_type": "markdown", "metadata": {}, "source": ["This notebook covers the following aspects:\n", "\n", "* implementation of the 1D acoustic wave equation\n", "* understanding the input parameters for the simulation and the plots that are generated\n", "* allowing you to explore the finite-difference method with optimal operator\n", "\n", "---"]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Optimising Operators\n", "\n", "Geller and Takeuchi (1995) developed criteria against which the accuracy of frequency-domain calculation of synthetic seismograms could be optimised. This approach was transferred to the time-domain finite-difference method for homogeneous and heterogeneous schemes by Geller and Takeuchi (1998). Look at an optimal operator and compare with the classic scheme. The space-time stencils are illustrated below\n", "\n", "\n", "\n", "$$Conventional-(1/dt^2)$$\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
t+dt1
t-2
t-dt1
x-dxxx+dx
\n", "\n", "$$Optimal-(1/dt^2)$$\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
t+dt1/1210/121/12
t-2/12-20/12-2/12
t-dt1/1210/121/12
x-dxxx+dx
\n", "\n", "\n", "$$Conventional-(1/dx^2)$$\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
t+dt
t1-21
t-dt
x-dxxx+dx
\n", "\n", "$$Optimal-(1/dx^2)$$\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
t+dt1/12-2/121/12
t10/12-20/1210/12
t-dt1/12-2/121/12
x-dxxx+dx
\n", "\n", "*The conventional 2nd order finite-difference operators for the 2nd derivative are compared with the optimal operators developed by Geller and Takeuchi (1998), see text for details.*\n", "\n", "\n", "Note that summing up the optimal operators one obtains the conventional operators. This can be interpreted as a smearing out of the conventional operators in space and time. The optimal operators lead to a locally implicit scheme, as the future of the system at $(x,t+dt)$ depends on values at time level \"$t+dt$, i.e., the future depends on the future. That sounds impossible, but it can be fixed by using a predictor-corrector scheme based on the first-order Born approximation. \n", "\n", "\n", "The optimal operators perform in a quite spectacular way. With very few extra floating point operations an accuracy improvement of almost an order of magnitude can be obtained. The optimal scheme performs substantially better than the conventional scheme with a 5-point operator.\n", "\n", "---"]}, {"cell_type": "code", "execution_count": null, "metadata": {"code_folding": []}, "outputs": [], "source": ["# Import Libraries (PLEASE RUN THIS CODE FIRST!) \n", "# ----------------------------------------------\n", "import numpy as np\n", "import matplotlib\n", "# Show Plot in The Notebook\n", "matplotlib.use(\"nbagg\")\n", "import matplotlib.pyplot as plt\n", "\n", "# Sub-plot Configuration\n", "# ----------------------\n", "from matplotlib import gridspec \n", "\n", "# Ignore Warning Messages\n", "# -----------------------\n", "import warnings\n", "warnings.filterwarnings(\"ignore\")"]}, {"cell_type": "code", "execution_count": null, "metadata": {"code_folding": []}, "outputs": [], "source": ["# Parameter Configuration \n", "# -----------------------\n", "nt = 501 # number of time steps\n", "eps = 1. # stability limit\n", "xs = 250 # source location in grid in x-direction\n", "xr = 450 # receiver location in grid in x-direction\n", "\n", "# Material Parameters\n", "# -------------------\n", "rho = 2500. # density\n", "c0 = 2000. # velocity\n", "mu = rho*(c0**2) # elastic modulus \n", "\n", "# Space Domain\n", "# ------------\n", "nx = 2 *xs # number of grid points in x-direction\n", "dx = 2.*nx/(nx) # calculate space increment\n", "\n", "# Calculate Time Step from Stability Criterion\n", "# --------------------------------------------\n", "dt = .5*eps*dx/c0"]}, {"cell_type": "code", "execution_count": null, "metadata": {"code_folding": []}, "outputs": [], "source": ["# Source Time Function \n", "# --------------------\n", "f0 = 1./(10.*dt) # dominant frequency of the source (Hz)\n", "t0 = 4./f0 # source time shift\n", "\n", "# Source Time Function (Gaussian)\n", "# -------------------------------\n", "src = np.zeros(nt)\n", "time = np.linspace(0 * dt, nt * dt, nt)\n", "# 1st derivative of a Gaussian\n", "src = -2.*(time-t0)*(f0**2)*(np.exp(-1.*(f0**2)*(time-t0)**2))"]}, {"cell_type": "code", "execution_count": null, "metadata": {"code_folding": []}, "outputs": [], "source": ["# Operator Error Calculation \n", "# --------------------------\n", "\n", "# Conventional FD Operators\n", "# -------------------------\n", "A0 = rho / (dt**2) * np.matrix\\\n", "(' 0. 1. 0.;\\\n", " 0. -2. 0.;\\\n", " 0. 1. 0.')\n", "K0 = mu / (dx**2) * np.matrix\\\n", "(' 0. 0. 0.;\\\n", " 1. -2. 1.;\\\n", " 0. 0. 0.')\n", "\n", "# Modified FD Operators\n", "# ---------------------\n", "A = 1. / 12. * rho / (dt**2) * np.matrix\\\n", "(' 1. 10. 1.;\\\n", " -2. -20. -2.;\\\n", " 1. 10. 1.')\n", "K = 1. / 12. * mu / (dx**2) * np.matrix\\\n", "(' 1. -2. 1.;\\\n", " 10. -20. 10.;\\\n", " 1. -2. 1.')\n", "\n", "# Calculate Operator Error\n", "# ------------------------\n", "dA = A0 - A # error of conventional operator A\n", "dK = K0 - K # error of conventional operator K\n", "d0 = (dA - dK) * (dt**2) / rho # basic error of modified operator"]}, {"cell_type": "code", "execution_count": null, "metadata": {"code_folding": []}, "outputs": [], "source": ["# Snapshot (RUN THIS CODE BEFORE SIMULATION!) \n", "# -------------------------------------------\n", "\n", "# Initialize Pressure Fields\n", "# --------------------------\n", "p = np.zeros(nx) # p at time n (now)\n", "pnew = p # p at time n+1 (present)\n", "pold = p # p at time n-1 (past)\n", "d2p = p # 2nd space derivative of p\n", "\n", "mp = np.zeros(nx) # mp at time n (now)\n", "mpnew= mp # mp at time n+1 (present)\n", "mpold= mp # mp at time n-1 (past)\n", "md2p = mp # 2nd space derivative of mp\n", "\n", "op = np.zeros(nx) # op at time n (now)\n", "opnew= op # op at time n+1 (present)\n", "opold= op # op at time n-1 (past)\n", "od2p = op # 2nd space derivative of op\n", "\n", "ap = np.zeros(nx) # op at time n (now)\n", "\n", "# Initialize model (assume homogeneous model)\n", "# -------------------------------------------\n", "c = np.zeros(nx)\n", "c = c + c0 # initialize wave velocity in model\n", "\n", "# Initialize Coordinate\n", "# ---------------------\n", "x = np.arange(nx)\n", "x = x * dx # coordinate in x-direction\n", "\n", "# Initialize Empty Seismogram\n", "# ---------------------------\n", "sp = np.zeros(nt)\n", "smp = np.zeros(nt)\n", "sop = np.zeros(nt)\n", "sap = np.zeros(nt)\n", "\n", "# Plot Position Configuration\n", "# ---------------------------\n", "plt.ion()\n", "fig2 = plt.figure(figsize=(12, 6))\n", "gs2 = gridspec.GridSpec(1, 1, hspace=0.3, wspace=0.3)\n", "\n", "# Plot 1D Wave Propagation\n", "# ------------------------\n", "# Note: comma is needed to update the variable\n", "ax3 = plt.subplot(gs2[0])\n", "up31,= ax3.plot(p, 'r') # plot pressure update each time step\n", "up32,= ax3.plot(mp, 'g') # plot pressure update each time step\n", "up33,= ax3.plot(op, 'b') # plot pressure update each time step\n", "up34,= ax3.plot(ap, 'k') # plot pressure update each time step\n", "ax3.set_xlim(0, nx)\n", "lim = 12. * src.max() * (dx) * (dt**2) / rho\n", "ax3.set_ylim(-lim, lim)\n", "ax3.set_title('Time Step (nt) = 0')\n", "ax3.set_xlabel('nx')\n", "ax3.set_ylabel('Amplitude')\n", "error1 = np.sum((np.abs(p - ap))) / np.sum(np.abs(ap)) * 100\n", "error2 = np.sum((np.abs(mp - ap))) / np.sum(np.abs(ap)) * 100\n", "error3 = np.sum((np.abs(op - ap))) / np.sum(np.abs(ap)) * 100\n", "ax3.legend((up31, up32, up33, up34),\\\n", "('3 point FD: %g %%' % error1,\\\n", " '5 point FD: %g %%' % error2,\\\n", " 'optimal FD: %g %%' % error3,\\\n", " 'analytical'), loc='lower right', fontsize=10, numpoints=1)\n", "\n", "plt.show()"]}, {"cell_type": "code", "execution_count": null, "metadata": {"tags": ["exercise"]}, "outputs": [], "source": ["# 1D Wave Simulation (RUN THIS CODE TO BEGIN SIMULATION!) \n", "# -------------------------------------------------------\n", "\n", "# Calculate Partial Derivatives\n", "# -----------------------------\n", "for it in range(nt):\n", " \n", " # 3 Point Operator FD scheme\n", " # --------------------------\n", " for i in range(3, nx - 2):\n", " d2p[i] = (1. * p[i + 1] - 2. * p[i] + 1. * p[i - 1]) / dx ** 2\n", " # Time Extrapolation\n", " pnew = 2. * p - pold + c**2 * d2p * dt**2\n", " # Add Source Term at xs\n", " pnew[xs] = pnew[xs] + src[it] * (dx) * (dt**2) / rho\n", " # Remap Time Levels\n", " pold, p = p, pnew\n", " # Set Boundaries Pressure Free\n", " p[0] = 0.\n", " p[nx-1] = 0.\n", " # Seismogram\n", " sp[it] = p[xr]\n", " \n", " # 5 Point Operator FD scheme\n", " # --------------------------\n", " for i in range(3, nx - 2):\n", " md2p[i] = (-1./12. * mp[i + 2] + 4./3. * mp[i + 1] - 5./2. * mp[i]\\\n", " +4./3. * mp[i - 1] - 1./12. * mp[i - 2]) / dx ** 2\n", " # Time Extrapolation\n", " mpnew = 2. * mp - mpold + c**2 * md2p * dt**2 \n", " # Add Source Term at xs\n", " mpnew[xs] = mpnew[xs] + src[it] * (dx) * (dt**2) / rho\n", " # Remap Time Levels\n", " mpold, mp = mp, mpnew\n", " # Set Boundaries Pressure Free\n", " mp[0] = 0.\n", " mp[nx-1] = 0.\n", " # Seismogram\n", " smp[it] = mp[xr]\n", " \n", " # Optimal Operator Scheme\n", " # -----------------------\n", " for i in range(3, nx - 2):\n", " od2p[i] = (1. * op[i + 1] - 2. * op[i] + 1. * op[i - 1]) / dx ** 2\n", " # Time Extrapolation\n", " opnew = 2. * op - opold + c**2 * od2p * dt**2\n", " # Calculate Corrector\n", " odp = op * 0.\n", " # Corrector at x-dx, x and x+dx\n", " for i in range(3, nx - 2): \n", " odp[i] = opold[i - 1: i + 2] * d0[:, 0]\\\n", " + op[i - 1: i + 2] * d0[:, 1]\\\n", " + opnew[i - 1: i + 2] * d0[:, 2]\n", " opnew = opnew + odp\n", " # Add Source Term at xs\n", " opnew[xs] = opnew[xs] + src[it] * (dx) * (dt**2) / rho\n", " # Remap Time Levels\n", " opold, op = op, opnew\n", " # Set Boundaries Pressure Free\n", " op[0] = 0.\n", " op[nx-1] = 0.\n", " # Seismogram\n", " sop[it] = op[xr]\n", " \n", " #####################################\n", " # Insert the Analytical Solution\n", " #####################################\n", " \n", " # Update Data For Wave Propagation Plot\n", " # -------------------------------------\n", " idisp = 2 # display frequency\n", " if (it % idisp) == 0:\n", " ax3.set_title('Time Step (nt) = %d' % it)\n", " up31.set_ydata(p)\n", " up32.set_ydata(mp)\n", " up33.set_ydata(op)\n", " \n", " plt.gcf().canvas.draw()"]}, {"cell_type": "code", "execution_count": null, "metadata": {"code_folding": [], "tags": ["solution"]}, "outputs": [], "source": []}], "metadata": {"kernelspec": {"display_name": "Python 3", "language": "python", "name": "python3"}}, "nbformat": 4, "nbformat_minor": 2}